import os
import shutil
import hashlib
import zipfile
#feature imports
import struct
import requests
import json
import logging

from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

offset_logo_presequence = [0x62, 0x61, 0x64, 0x5F, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x00, 0x00, 0x00]
offset_buttonMap_presequence = [0x00, 0x00, 0x00, 0x71, 0xDB, 0x8E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
offset_buttonMap_postsequence = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]

def patchCRC32(bisrv_content):
    x = crc32mpeg2(bisrv_content[512:len(bisrv_content):1])    
    bisrv_content[0x18c] = x & 255
    bisrv_content[0x18d] = x >> 8 & 255
    bisrv_content[0x18e] = x >> 16 & 255
    bisrv_content[0x18f] = x >> 24
    return bisrv_content

def crc32mpeg2(buf, crc=0xffffffff):
    for val in buf:
        crc ^= val << 24
        for _ in range(8):
            crc = crc << 1 if (crc & 0x80000000) == 0 else (crc << 1) ^ 0x104c11db7
    return crc

def QImageToRGB565Logo(inputQImage):
    print("Converting supplied file to boot logo format")
    # Need to increase the size to 512x200
    inputQImage = inputQImage.scaled(512, 200, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
    inputQImage = inputQImage.convertToFormat(QImage.Format_RGB16)
    rgb565Data = []
    for y in range(0, 200):
        for x in range(0, 512):
            pixel = inputQImage.pixelColor(x,y)
            pxValue = ((pixel.red() & 248) << 8) + ((pixel.green() & 252) << 3) + (pixel.blue() >> 3)
            rgb565Data.append(pxValue)
    print("Finished converting image to boot logo format")
    return rgb565Data   

def findSequence(needle, haystack, offset = 0):
    # Loop through the data array starting from the offset
    for i in range(len(haystack) - len(needle) + 1):
        readpoint = offset + i
        # Assume a match until proven otherwise
        match = True
        # Loop through the target sequence and compare each byte
        for j in range(len(needle)):
            if haystack[readpoint + j] != needle[j]:
                # Mismatch found, break the inner loop and continue the outer loop
                match = False
                break
        # If match is still true after the inner loop, we have found a match
        if match:
            # Return the index of the first byte of the match
            return readpoint
    # If we reach this point, no match was found
    return -1
    
def main():   
    index_path = "FOLDER"
    print(f"trying to read {index_path}")
    try:
        file_handle = open(index_path, 'rb')  # rb for read, wb for write
        bisrv_content = bytearray(file_handle.read(os.path.getsize(index_path)))
        file_handle.close()
        print("Finished reading file")
        # First, replace CRC32 bits with 00...
        bisrv_content[396] = 0x00
        bisrv_content[397] = 0x00
        bisrv_content[398] = 0x00
        bisrv_content[399] = 0x00
        print("Blanked CRC32")
        
        # Next identify the boot logo position, and blank it out too...
        print("start finding logo")
        badExceptionOffset = findSequence(offset_logo_presequence, bisrv_content)
        print("finished finding logo")
        if (badExceptionOffset > -1):  # Check we found the boot logo position
            bootLogoStart = badExceptionOffset + 16
            for i in range(bootLogoStart, bootLogoStart + 204800):
                bisrv_content[i] = 0x00
        else:  # If no boot logo found exit
            return False
            
        print(bootLogoStart)
        
        print("Blanked Bootlogo")
        
        # Next identify the emulator button mappings (if they exist), and blank them out too...
        preButtonMapOffset = findSequence(offset_buttonMap_presequence, bisrv_content)
        if preButtonMapOffset > -1:
            postButtonMapOffset = findSequence(offset_buttonMap_postsequence, bisrv_content, preButtonMapOffset)
            if postButtonMapOffset > -1:
                for i in range(preButtonMapOffset + 16, i < postButtonMapOffset):
                    bisrv_content[i] = 0x00
            else:
                return False
        else:
            return False
        
        # Next we'll look for (and zero out) the five bytes that the power
        # monitoring functions of the SF2000 use for switching the UI's battery
        # level indicator. These unfortunately can't be searched for - they're just
        # in specific known locations for specific firmware versions...
        prePowerCurve = findSequence([0x11, 0x05, 0x00, 0x02, 0x24], bisrv_content)
        if prePowerCurve > -1:
            powerCurveFirstByteLocation = prePowerCurve + 5
            if powerCurveFirstByteLocation == 0x35A8F8:
                # Seems to match mid-March layout...
                bisrv_content[0x35A8F8] = 0x00
                bisrv_content[0x35A900] = 0x00
                bisrv_content[0x35A9B0] = 0x00
                bisrv_content[0x35A9B8] = 0x00
                bisrv_content[0x35A9D4] = 0x00

            elif powerCurveFirstByteLocation == 0x35A954:
                # Seems to match April 20th layout...
                bisrv_content[0x35A954] = 0x00
                bisrv_content[0x35A95C] = 0x00
                bisrv_content[0x35AA0C] = 0x00
                bisrv_content[0x35AA14] = 0x00
                bisrv_content[0x35AA30] = 0x00

            elif powerCurveFirstByteLocation == 0x35C78C:
                # Seems to match May 15th layout...
                bisrv_content[0x35C78C] = 0x00
                bisrv_content[0x35C794] = 0x00
                bisrv_content[0x35C844] = 0x00
                bisrv_content[0x35C84C] = 0x00
                bisrv_content[0x35C868] = 0x00

            elif powerCurveFirstByteLocation == 0x35C790:
                # Seems to match May 22nd layout...
                bisrv_content[0x35C790] = 0x00
                bisrv_content[0x35C798] = 0x00
                bisrv_content[0x35C848] = 0x00
                bisrv_content[0x35C850] = 0x00
                bisrv_content[0x35C86C] = 0x00

            elif powerCurveFirstByteLocation == 0x3564EC:
                # Seems to match August 3rd layout...
                bisrv_content[0x3564EC] = 0x00
                bisrv_content[0x3564F4] = 0x00
                bisrv_content[0x35658C] = 0x00
                bisrv_content[0x356594] = 0x00
                bisrv_content[0x3565B0] = 0x00
            else:
                return False
        else:
            return False
        # If we're here, we've zeroed-out all of the bits of the firmware that are
        # semi-user modifiable (boot logo, button mappings and the CRC32 bits); now
        # we can generate a hash of what's left and compare it against some known
        # values...
        print("starting to compute hash")  
        sha256hasher = hashlib.new('sha256')
        sha256hasher.update(bisrv_content)
        bisrvHash = sha256hasher.hexdigest()
        print(f"Hash: {bisrvHash}")
        changeBootLogo(index_path, "/home/user/Documentos/img.png", 0);
        
        
        
    except (IOError, OSError):
        print("! Failed reading bisrv.")
        print("  Check the SD card and file are readable, and the file is not open in another program.")
        raise Exception_InvalidPath
        
        
def changeBootLogo(index_path, newLogoFileName, msgBox):
    # Confirm we arent going to brick the firmware by finding a known version
    # Load the new Logo
    print("Uploading new boot logo...")

    newLogo = QImage(newLogoFileName)
    # Convert to RGB565
    print("Converting boot logo...")

    rgb565Data = QImageToRGB565Logo(newLogo)
    # Change the boot logo
    print("Uploading boot logo...")

    file_handle = open(index_path, 'rb')  # rb for read, wb for write
    bisrv_content = bytearray(file_handle.read(os.path.getsize(index_path)))
    file_handle.close()
    logoOffset = findSequence(offset_logo_presequence, bisrv_content)
    bootLogoStart = logoOffset + 16
    
    for i in range(0, 512*200):
        data = rgb565Data[i].to_bytes(2, 'little')
        bisrv_content[bootLogoStart+i*2] = data[0]
        bisrv_content[bootLogoStart+i*2+1] = data[1]
    print("Updating BIOS file...")

    print("Patching CRC")    
    bisrv_content = patchCRC32(bisrv_content)
    print("Uploading BIOS file...")

    print("Writing bisrv to file")
    file_handle = open(index_path, 'wb')  # rb for read, wb for write
    file_handle.write(bisrv_content)    
    file_handle.close()

    return True
    
    
main()
